Skip to content

服务端日志规范

日志级别:

  • fatal - 属于系统致命错误,一般出现意味着系统基本等于挂掉了,需要人工立即介入处理
  • error - 系统逻辑出错、异常或者重要的错误信息
  • warn - 警告信息,如程序调用了一个即将作废的接口,接口的不当使用,运行状态不是期望的 - 但仍可继续处理等;
  • info - 有意义的事件信息,如程序启动,关闭事件,收到请求事件等;
  • debug - 调试信息,可记录详细的业务处理到哪一步了,以及当前的变量状态;
  • trace - 更详细的跟踪信息;

日志文件

  1. 日志文件推荐至少保存15天,因为有些异常具备以“周”为频次发生的特点。
  2. 应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:appName_logType_logName.log。
  3. logType:日志类型,推荐分类有stats/monitor/visit等;logName:日志描述。
  4. 这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。

滚动策略

虽然日志中能够保存系统运行时的关键信息,但是由于磁盘空间有限,所以我们不能无限制地保留日志,因此必须有日志滚动策略。日志滚动通常有以下几种模式:

  • 按照时间滚动,即每隔一定的时间建立一个新的日志文件,通常可以按照小时级别滚动或者天级别滚动,具体采取哪种方式取决于系统日志的打印量。如果系统日志比较少,可以采取天级别滚动;而如果系统日常量比较大,则建议采取小时级别滚动。
  • 按照单个日志文件大小滚动,即每当日志文件达到一定大小则建立一个新的日志文件,通常建议单个日志文件大小不要超过500M,日志文件过大的话,对于日志监控或者问题定位排查都可能会造成一定影响。
  • 按照时间和单个日志文件大小滚动,这种模式通常适用于希望保留一定时间的日志,但是又不希望单个日志文件过大的场景。  对于日志滚动策略来说,有2个比较关键的参数:最大保留日志数量和最大磁盘占用空间。这2个参数切记一定要设置,如果没有设置,则很有可能会出现把线上机器磁盘打满

打印时机

由于日志是为了方便我们了解系统当前的运行状况以及定位线上问题,所以日志打印的时机非常重要,如果滥用日志,则会导致日志内容过多,影响问题定位的效率;如果日志打印过少,则容易导致缺少关键日志,导致在线上定位问题时找不到问题根音。因此把握日志打印的时机至关重要,以下是常见的适合打印日志的时机:

  • 对trace/debug/info级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
javascript
if (logger.isDebugEnabled()) {    
   logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);   
}
// or
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
if (logger.isDebugEnabled()) {    
   logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);   
}
// or
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);

logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); 如果日志级别是warn,上述日志不会打印,但是会执行字符串拼接操作,如果symbol是对象,会执行toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。

  • http调用或者rpc接口调用

在程序调用其他服务或者系统的时候,需要打印接口调用参数和调用结果(成功/失败)。

  • 程序异常

在程序出现exception的时候,要么选择向上抛出异常,要么必须在catch块中打印异常堆栈信息。不过需要注意的是,最好不要重复打印异常日志,比如在catch块里既向上抛出了异常,又去打印错误日志(对外rpc接口函数入口处除外)。

  • 特殊的条件分支

程序进入到一些特殊的条件分支时,比如特殊的else或者switch分支,打印这种非预期的情况。

  • 关键执行路径及中间状态

在一些关键的执行路径以及中间状态也需要记录下关键日志信息,比如一个算法可能分为很多步骤,每隔步骤的中间输出结果是什么,需要记录下来,以方便后续定位跟踪算法执行状态。

  • 请求入口和出口

在函数或者对外接口的入口/出口处需要打印入口/出口日志,一来方便后续进行日志统计,同时也更加方便进行系统运行状态的监控。

日志的内容与格式

日志打印时机决定了能够根据日志去进行问题定位,而日志的内容决定了是否能够根据日志快速找出问题原因,因此日志内容也是至关重要的。通常来说,一行日志应该至少包括以下几个组成部分:

logTag、param、exceptionStacktrace

logTag为日志标识,用来标识此日志输出的场景或者原因,param为函数调用参数,exceptionStacktrace为异常堆栈。举例说明:

javascript
try {
 // ....
} catch (ex) {
    // 有关键logTag,有参数信息,有错误堆栈
    LOG.error("post request error!!!, url:[[}], param:[{}]", url, param, ex);
}
try {
 // ....
} catch (ex) {
    // 有关键logTag,有参数信息,有错误堆栈
    LOG.error("post request error!!!, url:[[}], param:[{}]", url, param, ex);
}

遵循原则

通常情况下在程序日志里记录一些比较有意义的状态数据:程序启动,退出的时间点;程序运行消耗时间;耗时程序的执行进度;重要变量的状态变化。 除此之外,在公共的日志里规避打印程序的调试或者提示信息。

谨慎地记录日志。生产环境禁止输出debug日志;有选择地输出info日志;如果使用warn来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。

说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?